#include <iostream>
#include <string>
#include <cassert>
#include <ctime>
#include <cstdlib>
#include <pthread.h>
#include <unistd.h>
#include <sys/time.h>

#define THREAD_NUM 8

// pthread_mutex_t 类型为互斥锁，被锁死的区域，只能有一个执行流进入

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;  // 互斥锁初始化

// 对全局变量直接操作难以保证线程安全性
int ticket = 1000;

// ThreadData，用于向线程函数传参
struct ThreadData
{
    std::string _mtx_name;
    pthread_mutex_t *_ptx;

    ThreadData(const std::string& mtx_name, pthread_mutex_t *ptx)
        : _mtx_name(mtx_name)
        , _ptx(ptx)
    {  }
};


void *getTickets(void *arg)
{
    ThreadData *pth = (ThreadData *)arg;
    while (true)
    {
        // 临界区代码加锁
        int n = pthread_mutex_lock(pth->_ptx);
        assert(n == 0);
        if (ticket > 0)
        {
            // 可能有多个执行流进入if判断
            // 其中一个将ticket变为0后，其他执行流在if语句内重新开始执行
            // 这样会造成ticket小于0，但if内部的代码依旧被执行的情况
            usleep(rand() % 1500);
            printf("%s, ticket:%d\n", pth->_mtx_name.c_str(), ticket);

            // 自加、自减指令，翻译为汇编后有三条指令，它们的工作分别是：
            // 1. 将ticket变量的内容从内存加载到CPU寄存机
            // 2. 在CPU内部进程++/--运算
            // 3. 将寄存器中算得的ticket的值写回到内存中去
            // 在上述三条指令每一条指令被执行完成后，CPU的调度器都有可能切换线程，而此时CPU寄存器中的数据会被暂时保存到task_struct中去
            // 那么在三条指令运行中间被切走的线程再次被调度，会将存在task_struct中的ticket值拿到CPU寄存器中，而不是一个线程被切走时ticket被写入到内存中的值
            ticket--;

            // 解锁
            n = pthread_mutex_unlock(pth->_ptx);
            assert(n == 0);
        }
        else
        {
            n = pthread_mutex_unlock(pth->_ptx);
            assert(n == 0);
            break;
        }

        // 抢票成功的后续工作
        usleep(rand() % 10000);
    }

    delete pth;
    return nullptr;
}

int main(void)
{
    // pthread_t t1, t2, t3, t4;
    // pthread_create(&t1, NULL, getTickets, (void *)"thread 1");
    // pthread_create(&t2, NULL, getTickets, (void *)"thread 2");
    // pthread_create(&t3, NULL, getTickets, (void *)"thread 3");
    // pthread_create(&t4, NULL, getTickets, (void *)"thread 4");
    // pthread_join(t1, NULL);
    // pthread_join(t2, NULL);
    // pthread_join(t3, NULL);
    // pthread_join(t4, NULL);

    // 种下随机数种子
    srand((unsigned int)time(nullptr) ^ getpid() ^ 18378);

    struct timeval tv1;
    gettimeofday(&tv1, nullptr);

    pthread_mutex_t mutex;   // 互斥量（锁）
    pthread_mutex_init(&mutex, nullptr);  // 初始化锁

    pthread_t tid[THREAD_NUM];
    for(int i = 0; i < THREAD_NUM; ++i)
    {
        std::string name = "thread";
        name += std::to_string(i + 1);

        ThreadData* pth = new ThreadData(name, &mutex);

        int n = pthread_create(tid + i, nullptr, getTickets, (void*)pth);
        assert(n == 0);
    }

    // 阻塞等待子线程退出
    for(int i = 0; i < THREAD_NUM; ++i)
    {
        int n = pthread_join(tid[i], nullptr);
        assert(n == 0);
    }

    pthread_mutex_destroy(&mutex);   // 互斥锁销毁

    struct timeval tv2;
    gettimeofday(&tv2, nullptr);

    uint64_t gap = (tv2.tv_sec - tv1.tv_sec) * 1000000 + (tv2.tv_usec - tv1.tv_usec);
    std::cout << "cast: " << gap << std::endl;

    return 0;
}
